Chapter 03. 코드에서 나는 악취

  • 악취가 나는 코드를 찾고 적절한 기법으로 없애보자.

1. 기이한 이름

- 이름만 보고도 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있도록 엄청나게 신경 써라.
- 마땅한 이름이 떠오르지 않는다면 설계와 같은 더 근본적인 문제가 숨어 있을 가능성이 있다.
- 이름을 잘 정리하다 보면 간결해지는 경우가 많다.

2. 중복 코드

- 중복 코드가 있으면, 차이점을 살펴봐야 하는 부담이 생기며 변경 시에도 다른 비슷한 코드를 확인보고 수정해야 한다.
- 중복 내용이 있다면, 함수로 추출하여 사용하기.
- 문장 슬라이드를 통해 비슷한 로직을 한곳에 모으고, 함수로 추출 가능한지 살펴보자.
- 서브 클래스에 중복 내용이 있다면 상위로 위치를 올려보자.

3. 긴 함수

- 짧은 함수는 코드를 이해하고, 공유하고, 선택하기가 쉽다.
- 요즘 언어에서는 함수 호출 비용이 많이 들지 않는다.
- 함수 이름을 잘 지어두면 굳이 내용을 안봐도 된다.
- 함수 이름은 동작 방식이 아니라 의도(목적)가 드러나게 짓는다.
- 함수 내용과 이름의 괴리가 크지 않도록 한다.
- 함수가 매개변수와 임시 변수를 많이 사용하면 추출 작업에 방해가 된다. 따라서 임시 변수의 수를 줄이고 매개변수를 객체로 묶어 정리한다.
- 복잡한 함수를 객체로 치환하는 방법도 있다.
- 코드가 단 한 줄이라도 설명할 필요가 있다면 함수로 추출하는 게 좋다.
- 조건문의 case 본문을 함수 호출 문 하나로 바꾼다.
- 반복문도 독립된 함수로 만들자.

4. 긴 매개변수 목록

- 다른 매개변수에서 값을 얻어오는 매개변수가 있다면, 매개변수를 질의 함수로 바꾸자. (매개변수를 사용하는 코드를 추출해서 사용하자)
- 객체를 통째로 넘기거나, 매개변수 객체를 만들어 넘기자.
- 플래그 성향의 매개변수가 있다면 함수를 나눠서 해당 매개변수를 없애자.
- 여러 함수를 클래스로 묶는다면 매개변수를 줄일 수 있다.    

5. 전역 데이터

- 함수 스코프에 넣어주자.

6. 가변 데이터

- 변수를 캡슐화하자. (getter/setter)
- 하나의 변수에 용도가 다른 값이 저장된다면 쪼개자.
- 갱신 코드는 다른 코드와 떨어뜨려 놓자.
- 변수로부터 얻어지는 파생 변수는 질의 함수로 바꾸자.
- 변수를 갱신하는 코드들의 스코프를 제한하자.
- 객체 내부의 객체라면 참조를 값으로 바꾸자.

7. 뒤엉킨 변경

- 수정할 일이 생겼을 때 여러 곳을 손봐야 한다면 해당 문제가 발생한 경우다.
- 맥락을 잘 구분하자.

8. 산탄총 수술

- 코드를 변경할 때마다 함께 수정해야 하는 부분이 코드 전반에 퍼져 있다면 발생한다.
- 함수와 필드를 한 모듈에 묶자.
- 여러 함수를 클래스로 묶거나, 변환 함수로 묶자.
- 코드를 재구성할 때에는 덩어리로 뭉쳐지는 것을 개의치 말자.

9. 기능 편애

- 모듈 안에서의 상호작용은 늘리고, 모듈끼리의 상호 작용은 최소화해야 하는데, 이것이 제대로 이루어지지 않은 경우 발생한다.
- 해당 함수가 원하는 적절한 모듈로 옮겨주자.

10. 데이터 뭉치

- 데이터는 뭉쳐 다니는 경향이 있다.
- 이런 친구들은 하나의 클래스로 묶어주자.
- 여러 데이터가 묶여서 의미를 만든다면 그것은 데이터 뭉치다.
- 데이터 뭉치를 묶을 때, 굳이 클래스 형태로 묶은 이유는 좋은 향기를 풍기기 위해서다. 해당 클래스로 이동할 친구가 있는지 살펴보고 이동시키자.

11. 기본형 집착

- 기본형만 고집하지 말고 클래스로 만들어서 사용하자.
- 부가 기능을 해당 클래스에 마음껏 묶을 수 있다.

12. 반복되는 switch문

- 최신 언어에서는 switch문 자체가 문제는 아니다. 중복된 switch 문이 문제다.
- 중복된 switch문은 조건절을 하나 추가할 때마다 다른 switch문들도 모두 찾아서 함께 수정해야 하기 때문이다. 이럴 때 다형성을 이용하도록 바꿔서 악취를 없애자.

13. 반복문

- 반복문보다는 filter, map 등의 파이프라인 연산을 이용하자.

14. 성의 없는 요소.

- 구조를 잡기 위해 사용된 프로그램 요소가 필요 없어 보인다면, 인라인으로 처리해주자.

15. 추측성 일반화

- '이거 나중에 필요할 거야'라는 생각으로 필요 없는 로직과 후킹 포인트를 만든 경우, 해당 악취가 발생한다.
- 하는 일 없는 추상 클래스는 합쳐버리고, 인라인 코드 활용으로 걸리적거리는 코드를 치워버리자.

16. 임시 필드

- 클래스의 특정 필드가 어떨 때는 값이 설정되고, 어떨 때는 설정되지 않는다면 해당 악취가 발생한다.
- 이런 필드들은 클래스 추출하기로 옮겨주고, 관련 함수도 같이 옮겨주자.
- 이런 필드의 유효성 체크 로직은 '유효하지 않은 경우'를 위한 대안 클래스를 만들어 제거하자.

17. 메시지 체인

- 객체를 통해 다른 객체를 얻는 과정이 연쇄적으로 이어질 때에 발생한다.
- ex) person.job.company.department...
- getter를 통해 캡슐화로 해결하자.
- 적절하게 함수로 추출하고 위치를 옮겨 체인을 숨겨보자.

18. 중개자

- 클래스가 다른 클래스에 구현을 위임하는 '중간 역할'만을 하고 있다면 직접 소통하도록 바꿔주자.

19. 내부자 거래

- 모듈 사이에 데이터 거래가 많으면 결합도가 높아진다.
- 적절하게 함수와 필드를 옮겨서 결합도를 낮춰보자.
- 여러 모듈이 관심사를 공유한다면 해당 부분을 제3의 모듈로 만들자.
- 상속 구조에서 자식 클래스는 부모 클래스의 데이터에 접근하고 싶은 경우가 많은데, 부모 품을 떠나야 할 때다. 인터페이스로 연결하여 결합도를 낮추자.

20. 거대한 클래스

- 클래스가 너무 많은 일을 담당하면 필드 수가 늘어나고 중복 코드가 생기기 쉽다.
- 연관된 필드를 묶고 상속 관계의 클래스로 추출하자.
- 코드량이 너무 많다면 중복을 제거하고 작은 메서드로 추출하자.

21. 서로 다른 인터페이스의 대안 클래스들

- 서로 다른 클래스의 두 메서드가, 하는 일은 비슷한데, 인터페이스가 다른 경우, 인터페이스를 통일시키고 가능하다면 추출하자.

22. 데이터 클래스

- 게터와 세터, 그리고 데이터 필드로 구성된 데이터 클래스는 다른 클래스가 함부로 다루는 경우가 많다.
- 필드를 캡슐화하고, 변경하지 못하는 필드는 세터를 제거해버리자.
- 다른 클래스에서 해당 데이터 클래스의 게터나 세터를 사용하는 코드를 찾아서 이를 데이터 클래스로 가져올 수 있는지 살펴보고 가능하다면 옮겨보자.

23. 상속 포기

- 부모 클래스의 특정 부분을 상속받기 원치 않는 경우에 발생한다.
- 상속하지 않을 부모 코드를 따로 분리해내어, 공통된 부분만 남도록 한다.
- 하지만 악취가 참을만 하므로 그다지 권하진 않는다.
- 부모의 인터페이스를 따르고 싶지 않으면 아예 상속 메커니즘에서 벗어나도록, 위임 클래스를 만들고 이를 이용하도록 만들자.

24. 주석

- 주석은 탈취제가 아니다.
- 주석 대신 함수 추출하기로 특정 코드 블록을 추출해보자.
- 주석 대신 함수 이름을 바꿔보자.
- 선행 조건을 명시하고 싶다면 어서션을 추가할 수도 있다.

results matching ""

    No results matching ""